#include <PianoProgram.h>
#include <unistd.h>

namespace pp
{
    ne::GlutApplication* PianoProgram::application = 0;
    ne::Engine* PianoProgram::engine = 0;
    ne::Keyboard* PianoProgram::keyboard = 0;
    Game* PianoProgram::game = 0;

    PianoProgram::PianoProgram(int argc, char *argv[])
    {
        application = new ne::GlutApplication(argc, argv);

        glutIdleFunc(PianoProgram::MainLoop);
        glutDisplayFunc(PianoProgram::Display);
        //glutFullScreen();

        if (argc > 1)
        {
            mSequenceFileName = argv[1];
        }
    }

    void PianoProgram::init()
    {
        engine = application->engine;
        keyboard = engine->getKeyboard();

        game = new Game();

        mpTimer = new ne::Timer();
        mpTimer->reset();

#if NE_PLATFORM == NE_PLATFORM_MACOSX
        mpMIDIInDevice = new ne::MIDIInDeviceMacOSX(0, "MIDI In Deveice MacOSX");
        mpMIDIInDevice->open();
#endif

        mpMIDIPlayer = new ne::MIDIPlayer();
        mpMIDIPlayer->sendEvent(ne::MIDI_CONTROLLER, 15, ne::MIDI_BANK_SELECT, 0);
        mpMIDIPlayer->sendEvent(ne::MIDI_CONTROLLER, 15, ne::MIDI_LSB_VALUE_FOR_CONTROLLER0, 0);

        mpMIDIPlayer->sendEvent(ne::MIDI_CONTROLLER, 15, ne::MIDI_MAIN_VOLUME, 127);
        mpMIDIPlayer->sendEvent(ne::MIDI_INSTRUMENT, 15, ne::MIDI_CLAVINET, 0);

        for (ne::byte i='A'; i<='Z'; ++i)
        {
            mKeyMap[i] = false;
        }

        mpNoteRenderer = new NoteRenderer();

        mpSequence.bind(new ne::MIDISequence(0, 0, mSequenceFileName, mSequenceFileName, false, 0));
        mpSequence->addListener(this);
    }

    void PianoProgram::onBackgroundThreadLoadingComplete(ne::Resource *pResource)
    {
        printf("PianoProgram::onBackgroundThreadLoadingComplete\n");
        mpNoteRenderer->build(mpSequence);
        mpMIDIPlayer->setSequence(mpSequence);
    }

    void PianoProgram::start()
    {
        mLoading = 0.0f;
        mArg = 1222;
        mpLoadThread = new ne::Thread(PianoProgram::LoadSequence, &mArg);
        game->start();
        glutMainLoop();
    }

    bool PianoProgram::tick()
    {
        if (mpLoadThread &&
            mpLoadThread->isFinished() &&
            mpLoadThread->joinable())
        {
            int* res;
            mpLoadThread->join((void**)&res);
            printf("ThreadReturn = %d\n", *res);
            delete mpLoadThread;
            mpLoadThread = 0;

            mpSequence->removeListener(this);
            mpMIDIPlayer->play();
        }

        double interval = mpTimer->getIntervalSeconds();
        
        game->tick();
        mpMIDIPlayer->tick();

        for (ne::byte i='A'; i<='Z'; ++i)
        {
            if (keyboard->isKeyDown(ne::Keyboard::ConvertASCIIKey(i)))
            {
                if (mKeyMap[i] == false)
                {
                    printf("NoteOn[%d]\n", i);
                    mpMIDIPlayer->sendEvent(ne::MIDI_NOTE_ON, 15, i, 127);
                    mKeyMap[i] = true;
                }
            }
            else
            {
                if (mKeyMap[i] == true)
                {
                    printf("NoteOff[%d]\n", i);
                    mpMIDIPlayer->sendEvent(ne::MIDI_NOTE_OFF, 15, i, 0);
                    mKeyMap[i] = false;
                }
            }
        }

        if (mpMIDIPlayer->isPlaying())
        {
            double startTicks = mpMIDIPlayer->getPlayedTicks();
            double endTicks = startTicks + 8 * mpSequence->getTPQN();
            double deltaY = 90.0 / mpSequence->getTPQN();
            mpNoteRenderer->update(startTicks, endTicks, deltaY);
        }
        else
        {
            mLoading = std::fmod(mLoading + 10.0f * interval, 100.0f);
        }

        return true;
    }

    bool PianoProgram::render()
    {
        game->render();
        if (mpMIDIPlayer->isPlaying())
        {
            mpNoteRenderer->render();
        }
        else
        {
            glPushMatrix();
            glColor4f(0.0f, 0.8f, 1.0f, 0.5f);
            glBegin(GL_QUADS);
                glVertex3f(-10.0f, mLoading, 1.0f);
                glVertex3f(-10.0f, 0.0f, 1.0f);
                glVertex3f(10.0f, 0.0f, 1.0f);
                glVertex3f(10.0f, mLoading, 1.0f);
            glEnd();
            glPopMatrix();
        }
        return true;
    }

    PianoProgram::~PianoProgram()
    {
        printf("PianoProgram::~PianoProgram\n");
        if (mpLoadThread)
        {
            mpLoadThread->kill(0);
            delete mpLoadThread;
        }
        mpSequence.setNull();
        delete mpNoteRenderer;
        delete mpMIDIPlayer;
#if NE_PLATFORM == NE_PLATFORM_MACOSX
        mpMIDIInDevice->close();
        delete mpMIDIInDevice;
#endif
        delete mpTimer;
        delete game;
    }

    void* PianoProgram::LoadSequence(void *arg)
    {
        printf("PianoProgram::LoadSequence arg[%d]\n", *(int *)(arg));
        GetSingleton().mpSequence->load(true);
        printf("PianoProgram::LoadedSequence\n");
        return arg;
    }

    void PianoProgram::MainLoop()
    {
        GetSingleton().tick();
        glutPostRedisplay();
    }

    void PianoProgram::Display()
    {
        GetSingleton().render();
        engine->getRenderSystem()->flush();
        engine->getRenderSystem()->finish();
        glutSwapBuffers();

        engine->getRenderSystem()->checkError();
    }

    PianoProgram& PianoProgram::GetSingleton()
    {
        assert(mpInstance);
        return *mpInstance;
    }

    PianoProgram* PianoProgram::GetSingletonPtr()
    {
        return mpInstance;
    }
}
